package local

import (
	"container/list"
	"context"
	"errors"
	"fmt"
	"runtime/debug"
	"strconv"
	"sync"
	"time"

	"gitee.com/gitee-go/core"
	"gitee.com/gitee-go/core/common"
	"gitee.com/gitee-go/core/model/pipeline"
	"gitee.com/gitee-go/core/runtime"
	"gitee.com/gitee-go/server/comm"
	comm2 "gitee.com/gitee-go/server/engine/comm"
	"gitee.com/gitee-go/utils"
	"gitee.com/gitee-go/utils/ioex"
	"github.com/go-git/go-git/v5/plumbing"
)

var (
	ErrPreBuild         = errors.New("build 准备失败")
	ErrNotFountPipeline = errors.New("流水线不存在")
	ErrPipelineNoRepo   = errors.New("流水线未配置仓库")
	ErrPipelineNoCnf    = errors.New("流水线配置为空")
	OutTime             = time.Hour * 1
)

type PreBuildEngine struct {
	ctx    context.Context
	lk     sync.Mutex
	tasks  *list.List
	bdegn  comm2.IBuildEngine
	cliegn *ClientEngine

	running ioex.SyncMap
}

func StartPreBuildEngine(ctx context.Context, bdegn comm2.IBuildEngine, cliegn *ClientEngine) *PreBuildEngine {
	if bdegn == nil {
		return nil
	}
	c := &PreBuildEngine{
		ctx:    ctx,
		bdegn:  bdegn,
		cliegn: cliegn,
		tasks:  list.New(),
	}
	go func() {
		for !ioex.CheckContext(c.ctx) {
			c.run()
			time.Sleep(time.Millisecond * 10)
		}
	}()
	core.Log.Info("PreBuildEngine Ready")
	return c
}

func (c *PreBuildEngine) run() {
	defer func() {
		if err := recover(); err != nil {
			core.LogPnc.Errorf("PreBuildEngine runTask err:%+v", err)
			core.LogPnc.Errorf("%s", string(debug.Stack()))
		}
	}()
	// 循环检测运行中的任务是不是已完成,已完成或超时就从列表中删除
	c.running.Range(func(key string, value interface{}) bool {
		v := value.(*taskPreBuild)
		if v.ctx.Err() != nil {
			if v.ctx.Err().Error() != "context canceled" {
				sendYmlFailedNotice(v.preBuild, errors.New("yaml解析超时"))
			}
			c.running.Delun(v.id)
			return false
		}
		if v.end {
			c.running.Delun(v.id)
			return false
		}
		return true
	})

	c.lk.Lock()
	defer c.lk.Unlock()
	// 如果运行中的任务未超过最大限制,就从等待队列取出任务运行,并放入运行队列
	if c.tasks.Len() > 0 && (c.running.Len() < comm.ParseLimit || comm.ParseLimit == 0) {
		h := c.tasks.Front()
		if h != nil {
			t := h.Value.(*taskPreBuild)
			t.start()
			c.running.Put(t.id, t)
		}
		c.tasks.Remove(h)
		core.Log.Debugf("PreBuildEngine waiting len :%v , running len : %v", c.tasks.Len(), c.running.Len())
	}
}

// http请求收到后,要把任务交给engine异步执行,http需要立马返回结果
func (c *PreBuildEngine) PutWebHook(ptr *runtime.PreBuild) error {
	defer func() {
		if err := recover(); err != nil {
			core.LogPnc.Errorf("PreBuildEngine Put err:%+v", err)
			core.LogPnc.Errorf("%s", string(debug.Stack()))
		}
	}()
	if ptr == nil {
		return ErrPreBuild
	}
	ctx, cancelFunc := context.WithTimeout(c.ctx, OutTime)
	t := &taskPreBuild{
		id:       utils.NewXid(),
		prt:      c,
		preBuild: ptr,
		ctx:      ctx,
		cancel:   cancelFunc,
	}
	core.Log.Debugf("PutWebHook repoName:%v ", ptr.GetRepository().Name)
	c.lk.Lock()
	defer c.lk.Unlock()
	c.tasks.PushBack(t)
	return nil
}

// http请求收到后,要把任务交给engine异步执行,http需要立马返回结果
func (c *PreBuildEngine) PutRebuild(pipelineVersionId string, userId int64) (*pipeline.TPipelineVersion, error) {
	defer func() {
		if err := recover(); err != nil {
			core.LogPnc.Errorf("PutRebuild err:%+v", err)
			core.LogPnc.Errorf("%s", string(debug.Stack()))
		}
	}()
	core.Log.Debugf("PutRebuild pipelineVersionId:%v ", pipelineVersionId)
	db := comm.DBMain.GetDB()
	tpv := &pipeline.TPipelineVersion{}
	get, err := db.Where("id = ?", pipelineVersionId).Get(tpv)
	if err != nil {
		return nil, err
	}
	if !get {
		return nil, ErrNotFountPipeline
	}
	if tpv.RepoId == "" {
		return nil, ErrPipelineNoRepo
	}
	tr := &pipeline.TRepo{}
	get, err = db.Where("id = ?", tpv.RepoId).Where("active !=0 and deleted != 1").Get(tr)
	if err != nil {
		return nil, err
	}
	if !get {
		return nil, ErrPipelineNoRepo
	}
	if tpv.JsonContent == "" {
		return nil, ErrPipelineNoCnf
	}
	tu := &pipeline.TUserToken{}
	_, err = db.Where("uid = ? ", userId).Get(tu)
	if err != nil {
		return nil, err
	}
	number := int64(0)
	_, err = db.
		SQL("SELECT max(number) FROM t_pipeline_version WHERE pipeline_id = ?", tpv.PipelineId).
		Get(&number)
	tpv.Id = utils.NewXid()
	tpv.Number = number + 1
	tpv.Status = common.PIPELINE_VERSION_STATUS_PENDING
	tpv.Error = ""
	tpv.Trigger = common.TRIGGER_TYPE_REBUILD
	tpv.CreateTime = time.Now()
	tpv.CreateUser = tu.Name
	tpv.CreateUserId = strconv.FormatInt(userId, 10)
	_, err = db.Insert(tpv)
	if err != nil {
		return nil, err
	}
	pb := &runtime.PreBuild{
		TriggerType: common.EVENTS_TYPE_REBUILD,
		Info: &runtime.PreInfo{
			Action:            tpv.Events,
			Events:            tpv.Events,
			PipelineId:        tpv.PipelineId,
			PipelineVersionId: tpv.Id,
			User: &runtime.PreUser{
				Id:       userId,
				UserName: tu.Name,
			},
			Note:          tpv.Note,
			CommitMessage: tpv.CommitMessage,
			Repository: &runtime.PreRepository{
				Id:         tr.Id,
				Ref:        tpv.Ref,
				Sha:        tpv.CommitSha,
				CloneURL:   tpv.RepoCloneUrl,
				Branch:     tpv.Branch,
				Name:       tpv.RepoName,
				RepoType:   tr.RepoType,
				RepoOpenid: tr.Openid,
			},
			TargetRepository: &runtime.PreRepository{
				Ref:      tpv.TargetRepoRef,
				Sha:      tpv.TargetRepoSha,
				CloneURL: tpv.TargetRepoCloneUrl,
				Name:     tpv.TargetRepoName,
			},
		},
	}
	ctx, cancelFunc := context.WithTimeout(c.ctx, OutTime)
	ts := &taskPreBuild{
		id:       utils.NewXid(),
		prt:      c,
		preBuild: pb,
		ctx:      ctx,
		cancel:   cancelFunc,
	}
	c.lk.Lock()
	defer c.lk.Unlock()
	c.tasks.PushBack(ts)

	return tpv, nil
}

// 把解析号的build提交给buildEngine执行
func (c *PreBuildEngine) PutBuild(pipelineId, branch string, userId int64) error {
	defer func() {
		if err := recover(); err != nil {
			core.LogPnc.Errorf("PutBuild err:%+v", err)
			core.LogPnc.Errorf("%s", string(debug.Stack()))
		}
	}()
	if branch == "" {
		return fmt.Errorf("分支不能为空")
	}
	core.Log.Debugf("PutBuild -- pipelineId :%v ,分支:%v ,userid: %v", pipelineId, branch, userId)
	db := comm.DBMain.GetDB()
	tp := &pipeline.TPipeline{}
	get, err := db.Where("id = ?", pipelineId).Get(tp)
	if err != nil {
		return err
	}
	if !get {
		return ErrNotFountPipeline
	}
	tr := &pipeline.TRepo{}
	get, err = db.Where("id = ?", tp.RepoId).Where("active != 0 and deleted != 1").Get(tr)
	if err != nil {
		return err
	}
	if !get {
		return ErrPipelineNoRepo
	}
	if tp.PipelineType == common.TRIGGER_TYPE_MANUAL && tp.JsonContent == "" {
		return ErrPipelineNoCnf
	}
	tu := &pipeline.TUserToken{}
	_, err = db.Where("uid = ? ", userId).Get(tu)
	if err != nil {
		return err
	}
	preBuild := &runtime.PreBuild{
		TriggerType: common.EVENTS_TYPE_BUILD,
		Info: &runtime.PreInfo{
			Events:            common.EVENTS_TYPE_BUILD,
			PipelineId:        pipelineId,
			PipelineVersionId: "",
			User: &runtime.PreUser{
				Id:       userId,
				UserName: tu.Name,
			},
			Note:          "",
			CommitMessage: "",
			Repository: &runtime.PreRepository{
				Id:         tr.Id,
				Ref:        plumbing.NewBranchReferenceName(branch).String(),
				Sha:        "",
				Name:       tr.Name,
				CloneURL:   tr.Url,
				Branch:     branch,
				URL:        tr.Url,
				RepoType:   tr.RepoType,
				RepoOpenid: tr.Openid,
			},
		},
	}
	ctx, cancelFunc := context.WithTimeout(c.ctx, OutTime)
	t := &taskPreBuild{
		id:       utils.NewXid(),
		ctx:      ctx,
		prt:      c,
		preBuild: preBuild,
		cancel:   cancelFunc,
	}
	c.lk.Lock()
	defer c.lk.Unlock()
	c.tasks.PushBack(t)
	return nil
}
